import { bus as eventBus, EVENTS as BUS_EVENTS } from './events';
import { Loop } from './systems/loop';
import {
  FeatureUnlockModel,
  RealmModel,
  StoryModel,
  CultivationSkillModel,
  InventoryModel,
  WalletModel,
  SpiritualRootModel,
  PlayerProgressModel,
  PlayerAttributesModel,
  CultivationModel,
  OfflineEarningsModel,
  BattleModel,
  SceneModel,
  BattleLevelModel,
  SectModel,
  UserInfoModel,
  EquipmentPanelModel,
} from './model';
import {
  PlayerStateSystem,
  SaveSystem,
  StorySystem,
  FeatureUnlockSystem,
  CultivationSkillSystem,
  InventorySystem,
  PlayerProgressSystem,
  SpiritualRootSystem,
  RealmSystem,
  CultivationSystem,
  BattleSystem,
  OfflineEarningsSystem,
  SceneSystem,
  PlayerAttributesSystem,
  RewardSystem,
  SectSystem,
  UserInfoSystem,
  EquipmentPanelSystem,
} from './systems';
import type { BaseModel } from './model/base';
import type { BaseSystem } from './systems/base';

export class GameKernel {
  public static readonly inst = new GameKernel();
  private isRunning = false;

  /** 事件总线 */
  readonly bus = eventBus;

  readonly realmModel = new RealmModel(this.bus);
  readonly storyModel = new StoryModel(this.bus);
  readonly featureUnlockModel = new FeatureUnlockModel(this.bus);
  readonly cultivationSkillModel = new CultivationSkillModel(this.bus);
  readonly inventoryModel = new InventoryModel(this.bus);
  readonly walletModel = new WalletModel(this.bus);
  readonly spiritualRootModel = new SpiritualRootModel(this.bus);
  readonly playerProgressModel = new PlayerProgressModel(this.bus);
  readonly playerAttributesModel = new PlayerAttributesModel(this.bus);
  readonly cultivationModel = new CultivationModel(this.bus);
  readonly offlineEarningsModel = new OfflineEarningsModel(this.bus);
  readonly battleModel = new BattleModel(this.bus);
  readonly sceneModel = new SceneModel(this.bus);
  readonly battleLevelModel = new BattleLevelModel(this.bus);
  readonly sectModel = new SectModel(this.bus);
  readonly userInfoModel = new UserInfoModel(this.bus);
  readonly equipmentPanelModel = new EquipmentPanelModel(this.bus);
  readonly models: BaseModel[] = [
    this.realmModel,
    this.storyModel,
    this.featureUnlockModel,
    this.cultivationSkillModel,
    this.inventoryModel,
    this.walletModel,
    this.playerProgressModel,
    this.spiritualRootModel,
    this.playerAttributesModel,
    this.cultivationModel,
    this.offlineEarningsModel,
    this.battleModel,
    this.sceneModel,
    this.battleLevelModel,
    this.sectModel,
    this.userInfoModel,
    this.equipmentPanelModel,
  ];

  /** 存档 */
  readonly saveSystem = new SaveSystem(this.models);

  /** 数据中心(中间层) */
  readonly playerStateSystem = new PlayerStateSystem();

  readonly storySystem = new StorySystem(this.bus, this.playerStateSystem);
  readonly featureUnlockSystem = new FeatureUnlockSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly cultivationSkillSystem = new CultivationSkillSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly inventorySystem = new InventorySystem(
    this.bus,
    this.playerStateSystem
  );
  readonly playerProgressSystem = new PlayerProgressSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly spiritualRootSystem = new SpiritualRootSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly realmSystem = new RealmSystem(this.bus, this.playerStateSystem);
  readonly cultivationSystem = new CultivationSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly battleSystem = new BattleSystem(this.bus, this.playerStateSystem);
  readonly offlineEarningsSystem = new OfflineEarningsSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly sceneSystem = new SceneSystem(this.bus, this.playerStateSystem);
  readonly playerAttributesSystem = new PlayerAttributesSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly rewardSystem = new RewardSystem(this.bus, this.playerStateSystem);
  readonly sectSystem = new SectSystem(this.bus, this.playerStateSystem);
  readonly userInfoSystem = new UserInfoSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly equipmentPanelSystem = new EquipmentPanelSystem(
    this.bus,
    this.playerStateSystem
  );
  readonly systems: BaseSystem[] = [
    this.storySystem,
    this.featureUnlockSystem,
    this.cultivationSkillSystem,
    this.inventorySystem,
    this.spiritualRootSystem,
    this.playerProgressSystem,
    this.realmSystem,
    this.cultivationSystem,
    this.battleSystem,
    this.offlineEarningsSystem,
    this.sceneSystem,
    this.playerAttributesSystem,
    this.rewardSystem,
    this.sectSystem,
    this.equipmentPanelSystem,
  ];

  /** 游戏循环 */
  readonly loop = new Loop(this.systems);

  constructor() {
    this.bus.on(BUS_EVENTS.OFFLINE_EARNINGS_GET_EARNINGS_SUCCESS, () => {
      this.runLoop();
      this.setOfflineTime();
    });

    this.bus.on(BUS_EVENTS.CULTIVATION_COMPLETED, () => {
      this.setOfflineTime();
    });
  }

  /** 注册数据中心 */
  private registerDataCenter(): void {
    this.models.forEach((m) => this.playerStateSystem.registerModel(m.name, m));
  }

  /** 所有系统初始化 */
  private initSystems(): void {
    this.systems.forEach((s) => s.init?.());
  }

  /** 所有系统初始化之后 */
  private initAfterSystems(): void {
    this.systems.forEach((s) => s.initAfter?.());
  }

  /** 加载存档 */
  public load(data: string): void {
    this.saveSystem.load(data);
    this.bus.emit(EVENTS.GAME_LOADED);
  }

  /** 保存存档 */
  public save(): string {
    return this.saveSystem.save();
  }

  /** 启动游戏循环和自动保存 */
  public runLoop(): void {
    this.loop.start();
  }

  /** 设置离线时间并保存 */
  private setOfflineTime(): void {
    if (!this.isRunning) return;
    this.offlineEarningsModel.setOfflineTime(Date.now());
    const data = this.save();
    this.bus.emit(EVENTS.GAME_MANUAL_SAVE, data);
  }

  public start(): void {
    if (this.isRunning) return;
    this.isRunning = true;
    this.registerDataCenter();
    this.initSystems();
    this.initAfterSystems();
    this.bus.emit(BUS_EVENTS.GAME_START);
    /** 判断是否有离线奖励，如果有离线奖励，领完之后再启动循环 */
    const hasEarnings = this.offlineEarningsModel.hasEarnings();
    if (hasEarnings) return;
    this.setOfflineTime();
    this.runLoop();
  }
}

export const EVENTS = BUS_EVENTS;
